/*
 * Copyright (C) 2008 Derek James and Philip Tucker
 * 
 * This file is part of ANJI (Another NEAT Java Implementation).
 * 
 * ANJI is free software; you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA
 * 
 * created by Philip Tucker on Oct 22, 2008
 */
package com.anji.moorph;

import org.apache.log4j.Logger;
import org.jgap.ChromosomeMaterial;
import org.jgap.Configuration;
import org.jgap.InvalidConfigurationException;
import org.jgap.NaturalSelector;
import org.jgap.event.EventManager;
import org.jgap.impl.CloneReproductionOperator;
import org.jgap.impl.WeightedRouletteSelector;

import com.anji.integration.SimpleSelector;
import com.anji.util.Properties;
import com.anji.util.Randomizer;

/**
 * Extension of JGAP configuration with MOORPH-specific features added.
 * 
 * @author Philip Tucker
 */
public class MoorphConfiguration extends Configuration {

  private static final long serialVersionUID = 1695749006704309626L;

  private static final Logger logger =
      Logger.getLogger(MoorphConfiguration.class);

  /**
   * default survival rate
   */
  public static final float DEFAULT_SURVIVAL_RATE = 0.20f;

  /**
   * default population size
   */
  public static final int DEFAULT_POPUL_SIZE = 100;

  /**
   * properties key, survival rate
   */
  public static final String SURVIVAL_RATE_KEY = "survival.rate";

  /**
   * properties key, population size
   */
  public static final String POPUL_SIZE_KEY = "popul.size";

  /**
   * properties key, speciation chromosome compatibility excess coefficient
   */
  public final static String CHROM_COMPAT_EXCESS_COEFF_KEY =
      "chrom.compat.excess.coeff";

  /**
   * properties key, speciation chromosome compatibility disjoint coefficient
   */
  public final static String CHROM_COMPAT_DISJOINT_COEFF_KEY =
      "chrom.compat.disjoint.coeff";

  /**
   * properties key, speciation chromosome compatibility common coefficient
   */
  public final static String CHROM_COMPAT_COMMON_COEFF_KEY =
      "chrom.compat.common.coeff";

  /**
   * properties key, speciation threshold
   */
  public final static String SPECIATION_THRESHOLD_KEY = "speciation.threshold";

  /**
   * properties key, elitism enabled
   */
  public final static String ELITISM_KEY = "selector.elitism";

  /**
   * properties key, minimum size a specie must be to produce an elite member
   */
  public final static String ELITISM_MIN_SPECIE_SIZE_KEY =
      "selector.elitism.min.specie.size";

  /**
   * properties key, enable weighted selection process
   */
  public final static String WEIGHTED_SELECTOR_KEY = "selector.roulette";

  private Properties props;

  private CloneReproductionOperator cloneOper = new CloneReproductionOperator();

  private MoorphCrossoverReproductionOperator crossoverOper =
      new MoorphCrossoverReproductionOperator();

  private MoorphGenes moorphGenes;

  /**
   * Initialize mutation operators.
   * 
   * @throws InvalidConfigurationException
   */
  private void initMutation() throws InvalidConfigurationException {
    // TODO(ptucker)
    // addMutationOperator();
  }

  /**
   * See <a href=" {@docRoot} /params.htm" target="anji_params">Parameter
   * Details </a> for specific property settings.
   * 
   * @param newProps configuration parameters; newProps[SURVIVAL_RATE_KEY]
   * should be < 0.50f
   * @throws InvalidConfigurationException
   */
  private void init(Properties newProps) throws InvalidConfigurationException {
    props = newProps;

    Randomizer r = (Randomizer) props.singletonObjectProperty(Randomizer.class);
    setRandomGenerator(r.getRand());
    setEventManager(new EventManager());

    moorphGenes = new MoorphGenes(props);
    setIdFactory(moorphGenes.getIdFactory());
    
    // make sure numbers add up
    float survivalRate =
        props.getFloatProperty(SURVIVAL_RATE_KEY, DEFAULT_SURVIVAL_RATE);
    float crossoverSlice = 1.0f - (2.0f * survivalRate);
    if (crossoverSlice < 0.0f) {
      throw new InvalidConfigurationException("survival rate too large: "
          + survivalRate);
    }

    // selector
    boolean isWeighted = props.getBooleanProperty(WEIGHTED_SELECTOR_KEY, false);
    NaturalSelector selector =
        isWeighted ? new WeightedRouletteSelector() : new SimpleSelector();
    selector.setSurvivalRate(survivalRate);
    selector.setElitism(props.getBooleanProperty(ELITISM_KEY, true));
    selector.setElitismMinSpecieSize(props.getIntProperty(
        ELITISM_MIN_SPECIE_SIZE_KEY, 6));
    setNaturalSelector(selector);

    // reproduction
    cloneOper.setSlice(survivalRate);
    crossoverOper.setSlice(crossoverSlice);
    addReproductionOperator(cloneOper);
    addReproductionOperator(crossoverOper);

    // mutation
    initMutation();

    // population
    setPopulationSize(props.getIntProperty(POPUL_SIZE_KEY, DEFAULT_POPUL_SIZE));
    load();
    ChromosomeMaterial sample = null; // TODO(ptucker)
    setSampleChromosomeMaterial(sample);
    store();

    // speciation parameters
    initSpeciationParms();
  }

  /**
   * See <a href=" {@docRoot} /params.htm" target="anji_params">Parameter
   * Details </a> for specific property settings.
   * 
   * @param newProps
   * @see MoorphConfiguration#init(Properties)
   * @throws InvalidConfigurationException
   */
  public MoorphConfiguration(Properties newProps)
      throws InvalidConfigurationException {
    init(newProps);
  }

  private void initSpeciationParms() {
    try {
      getSpeciationParms().setSpecieCompatExcessCoeff(
          props.getDoubleProperty(CHROM_COMPAT_EXCESS_COEFF_KEY));
    } catch (RuntimeException e) {
      logger.info("no speciation compatibility threshold specified", e);
    }
    try {
      getSpeciationParms().setSpecieCompatDisjointCoeff(
          props.getDoubleProperty(CHROM_COMPAT_DISJOINT_COEFF_KEY));
    } catch (RuntimeException e) {
      logger.info("no speciation compatibility threshold specified", e);
    }
    try {
      getSpeciationParms().setSpecieCompatCommonCoeff(
          props.getDoubleProperty(CHROM_COMPAT_COMMON_COEFF_KEY));
    } catch (RuntimeException e) {
      logger.info("no speciation compatibility threshold specified", e);
    }
    try {
      getSpeciationParms().setSpeciationThreshold(
          props.getDoubleProperty(SPECIATION_THRESHOLD_KEY));
    } catch (RuntimeException e) {
      logger.info("no speciation compatibility threshold specified", e);
    }
  }

  /**
   * @return clone reproduction operator used to create mutated asexual
   * offspring
   */
  CloneReproductionOperator getCloneOperator() {
    return cloneOper;
  }

  /**
   * @return crossover reproduction operator used to create mutated sexual
   * offspring
   */
  MoorphCrossoverReproductionOperator getCrossoverOperator() {
    return crossoverOper;
  }

  /**
   * Load from persistence.
   * 
   * @throws InvalidConfigurationException
   * @throws
   */
  public void load() throws InvalidConfigurationException {
    try {
      moorphGenes.load();
    } catch (Exception ex) {
      throw new InvalidConfigurationException("error loading IDs"
          + ex.getMessage());
    }
  }

  /**
   * Store to persistence.
   * 
   * @throws InvalidConfigurationException
   */
  public void store() throws InvalidConfigurationException {
    try {
      moorphGenes.store();
    } catch (Exception e) {
      String msg = "error storing configuration";
      logger.error(msg, e);
      throw new InvalidConfigurationException(msg);
    }
  }

  public MoorphGenes getMoorphGenes() {
    return moorphGenes;
  }

}
